# -*- coding: utf-8 -*-
from socket import socket
import time
import datetime
import json
from threading import *
from ctypes import *
import logging
# import pandas as pd

# 设置日志输出格式
logging.basicConfig(format = '%(asctime)s.%(msecs)03d [%(levelname)s] [%(filename)s:%(lineno)d] %(message)s',
                    datefmt = '%m-%d %H:%M:%S')
# 设置日志打印级别
logging.getLogger("test").setLevel(logging.DEBUG)
# 设置日志输出器的名字，可以在项目中配置多个不同的日志输出器
logger = logging.getLogger("test")


'''
# 【从excel文件获取测试数据】
def get_test_data(fileName, sheetName, caseName):
    df = pd.read_excel(fileName, sheet_name = sheetName, keep_default_na = False)
    result = df['caseName'].str.contains(caseName)
    result.fillna(value = False, inplace = True)
    df = df[result]
    # print(df)
    dict = {}
    for i in df.columns:
        v = str(df[i].values[0])
        if i == 'caseNO':
            continue
        else:
            dict[i] = v
    
    json_str = json.dumps(dict)
    # print(json_str)
    # 返回值示例：
    # {"caseName": "Q001001", "EVENT": "Q001001", "PAR1": "1", "PAR2": "2", "PAR3": "3", "OUT": "0", "expect_content": "0", "TIMEOUT": "2"}
    return json_str
    # DU_S402_NEW(json_str)
'''


#【与服务端进行连接】
def create_connect():
    logger.info("Connecting to the server...")
    skt = socket()
    # server_addr = ("172.16.100.145",51000)    # 联调的Linux服务器
    server_addr = ("172.16.100.101",51000)      # 岚图
    skt.settimeout(5)

    i = 0
    while True:
        try:
            i += 1
            skt.connect(server_addr)
        except Exception as e:
            if e.args[0] == 'timed out':
                time.sleep(5)                   # 连接失败则10秒后重连
                logger.warning("The {} time connection failed.{}".format(i, e))
                continue
            else:
                time.sleep(10)                  # 连接失败则10秒后重连
                logger.warning("The {} time connection failed.{}".format(i, e))
                continue
        else:
            logger.info("Successfully connected at the {} time.".format(i))
            return skt


# 【接收服务端消息】
def recv_from_server():
    global thread_end_flag
    while True:
        if thread_end_flag:
            break
        try:
            data = socket_client.recv(1024).decode()
            # logger.info('recv() success: {}'.format(data))
        except Exception as e:
            if e.args[0] == 'timed out':
                logger.warning("recv() failed: {}".format(e))
                continue
            else:
                # logger.warning("recv() failed: {}".format(e))
                # time.sleep(0.5)
                continue  # 断联了，继续接收消息，直到重连成功
        else:
            if data:
                analyze_data(data)
            # else:
            #     logger.warning("The received data is empty.")


# 【对接收的数据进行解析】
def analyze_data(data):
    global Event_TID
    data_list = data.split("\r\n")               # 用换行符切分消息字符串，生成list
    for s in data_list:
        if s == "":
            data_list.remove(s)                  # 去除列表里面的空字符串,留下有效数据

    for data_unique in data_list:
        try:
            # logger.info("data_unique:{}".format(data_unique))
            data_dict = json.loads(data_unique)  # json_str转dict
        except Exception as e:
            logger.warning("解析错误：".format(e))
        else:
            FuncCode_recv = data_dict.get("FuncCode")
            HeadFlag_recv = data_dict.get("HeadFlag")
            TID_recv = data_dict.get("TID")
            DataBody_rev = data_dict.get("DataBody")
            # logger.warning("------data----: {}-{}".format(FuncCode_recv, HeadFlag_recv))

            # 把数据解析出来后，根据内容进行处理。可能收到以下三类消息:

            # --- 服务端对心跳请求的应答
            if (FuncCode_recv == "S401") & (HeadFlag_recv == "DUR"):
                S401_DUR_dict[FuncCode_recv] = DataBody_rev
                event_S401.set()
            # --- 服务端对测试事件推送的应答
            elif (FuncCode_recv == "S402") & (HeadFlag_recv == "DUR") & (TID_recv == Event_TID):
                S402_DUR_dict[FuncCode_recv] = DataBody_rev
                event_S402.set()
            # --- 服务端对测试结果的反馈, 此处需要对发来的数据进行校验
            elif (FuncCode_recv == "S403") & (HeadFlag_recv == "TU") & (TID_recv == Event_TID):
                S403_DUR_dict[FuncCode_recv] = data_dict
                event_S403.set()
            else:
                logger.warning("Received other unexpected data: {}-{}".format(HeadFlag_recv, FuncCode_recv, TID_recv))


# 【把消息发给服务端】
def send_to_server(HeadFlag, FuncCode, TID, DataBody):
    time_now = datetime.datetime.now().strftime("%Y%m%d%H%M%S%f")[2:17]
    DataLen = len(json.dumps(DataBody).replace(' ','')) # int类型，如78
    DataLen = "%04d" % DataLen                          # 格式化成4字符str，如'0078'
    CRC = crc16_ccitt(DataBody)
    data = {"HeadFlag":HeadFlag,
            "Time":time_now,
            "FuncCode":FuncCode,
            "Version":"V1.0",
            "TID":TID,
            "DataLen":DataLen,
            "DataBody":DataBody,
            "CRC":CRC}
    # ---------发数据---------
    str_json = json.dumps(data)
    try:
        socket_client.send(str_json.encode() + b'\r\n')
    except Exception as e:
        logger.error("send() failed: {} -- {}".format(FuncCode, e))
        return 'Fail'      # send失败通常是IOError，是因为服务端连接断开了
    else:
        return 'Success'   # 只表示发送成功了，对方是否应答成功需要再判断


# 【专门发送心跳请求】
def DU_S401():
    i = 0
    global socket_client                    # 这里需要再声明一次，否则重连时会报错
    global thread_end_flag
    while True:
        if thread_end_flag:
            break
        HeadFlag = 'DU'
        FuncCode = "S401"
        TID = str(int(time.time()*1000))
        DataBody = {"Name":"Robot",
                    "Manufacturer":"LY",
                    "ProjectCode":"LY000001",
                    "VehicleModel":"LY_HKMJ_001"}
        send_result = send_to_server(HeadFlag, FuncCode, TID, DataBody)
        if send_result == 'Fail':           # send失败，说明连接出现了问题
            i += 1
            if i > 3:
                logger.warning("DU-S401 has failed three times in a row. Ready to reconnect...")
                # time.sleep(1)
                socket_client.close()       # 先关掉旧的socket
                socket_client = create_connect()
                logger.info("Reconnect successfully")
                continue
        if send_result == 'Success':
            event_S401.wait(timeout = 5)    # 等待recv()接收消息成功的反馈
            if event_S401.is_set():
                logger.info("DU-S401 success √ ")
                event_S401.clear()          # event_S401重新置为False
                i = 0
                time.sleep(1)
            else:
                i += 1
                logger.warning("DU-S401 response timeout for the {} time".format(i))
                if i > 3:
                    logger.warning("DU-S401 response timeout for the {} time. Ready to reconnect...")
                    # time.sleep(1)
                    socket_client.close()   # 先关掉旧的socket
                    socket_client = create_connect()
                    logger.warning("Reconnect successfully")
                    continue


# 【向服务端推送测试事件】 由RobotFramework调用，需要用户自己传参
def DU_S402(Event, InputPar1 = '0', InputPar2 = '0', InputPar3 = '0', OutputPar = '0', Timeout = '5'):
    for loop in range(3):
        HeadFlag = 'DU'
        FuncCode = "S402"
        # 把robot传入的16进制 转成 10进制
        InputPar1 = str(int(InputPar1, 16))
        InputPar2 = str(int(InputPar2, 16))
        InputPar3 = str(int(InputPar3, 16))
        OutputPar = str(int(OutputPar, 16))

        DataBody = {"Event":Event,
                    "Request":{'Timeout':Timeout, 
                                'Input': {'InputPar1':InputPar1, 'InputPar2':InputPar2, 'InputPar3':InputPar3},
                                'Output':OutputPar
                                }
                    }
        # print(DataBody)
        global Event_TID
        Event_TID = str(int(time.time()*1000))
        send_result = send_to_server(HeadFlag, FuncCode, Event_TID, DataBody)
        if send_result == 'Fail':
            continue                            # 发送失败，可试三次
        event_S402.wait(timeout = 5)
        if not event_S402.is_set():
            logger.warning("DU-S402, Event:【{}】- TID:{} No response received for the {} time".format(Event, Event_TID, loop+1))
            continue                            # 反馈超时，可试三次

        rev_databody = S402_DUR_dict.get(FuncCode)
        event_S402.clear()
        logger.info("DU-S402, Event:【{}】- TID:{} pushed successfully".format(Event, Event_TID))
        if rev_databody.get('ResCode') != '0':  # DU-S402数据被服务端检出有错误
            rev_ErrData = rev_databody.get('ErrData')
            logger.warning("DU-S402, Event:【{}】- TID:{} data error: {}!".format(Event, Event_TID, rev_ErrData))
            return 'Bolck'
        if (Event == "T001"):                   # T001事件，不需要S403响应
            return
        # 如果服务端应答表明测试请求DU-S402没问题，就等服务端发来测试结果
        res = TUR_S403(Timeout)
        if res == 'Fail':
            logger.warning("Event:【{}】- TID:{} don't receive test result in {} seconds!".format(Event, Event_TID, Timeout))
            logger.warning("Event:【{}】- TID:{} test failed !".format(Event, Event_TID))
            return 'Block'
        else:
            return res

    # 连续3次都没收到应答，判断服务端已断连
    logger.warning('DU-S402 No response received, Service maybe down!')


# 【向服务端推送测试事件】 由RobotFramework调用，需要用户自己传参
def DU_S402_NEW(testData):
    testData = json.loads(testData)             # 转成字典

    Event = testData['EVENT']
    Timeout = testData['TIMEOUT']
    # 把robot传入的16进制 转成 10进制
    InputPar1 = str(int(testData['PAR1'], 16))
    InputPar2 = str(int(testData['PAR2'], 16))
    InputPar3 = str(int(testData['PAR3'], 16))
    OutputPar = str(int(testData['OUT'], 16))

    DataBody = {"Event":Event,
                "Request":{'Timeout':Timeout,
                            'Input': {'InputPar1':InputPar1, 'InputPar2':InputPar2, 'InputPar3':InputPar3},
                            'Output':OutputPar
                            }
                }
    # logger.info('DataBody': DataBody)
    global Event_TID
    Event_TID = str(int(time.time()*1000))

    for loop in range(3):
        HeadFlag = 'DU'
        FuncCode = "S402"
        send_result = send_to_server(HeadFlag, FuncCode, Event_TID, DataBody)
        if send_result == 'Fail':
            continue                            # 发送失败，可试三次
        event_S402.wait(timeout = 5)
        if not event_S402.is_set():
            logger.warning("DU-S402, Event:【{}】- TID:{} No response received for the {} time".format(Event, Event_TID, loop+1))
            continue                            # 反馈超时，可试三次

        rev_databody = S402_DUR_dict.get(FuncCode)
        event_S402.clear()
        logger.info("DU-S402, Event:【{}】- TID:{} pushed successfully".format(Event, Event_TID))
        if rev_databody.get('ResCode') != '0':  # DU-S402数据被服务端检出有错误
            rev_ErrData = rev_databody.get('ErrData')
            logger.warning("DU-S402, Event:【{}】- TID:{} data error: {}!".format(Event, Event_TID, rev_ErrData))
            return 'Bolck'
        if (Event == "T001"):                   # T001事件，不需要S403响应
            return

        # 如果服务端应答表明测试请求DU-S402没问题，就等服务端发来测试结果
        res = TUR_S403(Timeout)
        if res == 'Fail':
            logger.warning("Event:【{}】- TID:{} don't receive test result in {} seconds!".format(Event, Event_TID, Timeout))
            logger.warning("Event:【{}】- TID:{} test failed !".format(Event, Event_TID))
            return 'Block'
        else:
            return res

    # 连续3次都没收到应答，判断服务端已断连
    logger.warning('DU-S402 No response received, Service maybe down!')


# 【对服务器的反馈请求(TU)S403，做出应答(TUR)S403】
def TUR_S403(Timeout):
    Timeout = int(Timeout) + 1
    event_S403.wait(timeout = Timeout)
    if not event_S403.is_set():
        return 'Fail'
    else:
        data_dict = S403_DUR_dict.get('S403')
    event_S403.clear()
    logger.info('TU-S403: {}'.format(data_dict))
    DataBody_rev = data_dict.get("DataBody")
    DataLen_rev = data_dict.get("DataLen")
    CRC_rev = data_dict.get("CRC")

    # -------- 数据校验，应答 --------
    res_check_datalen = check_datalen(DataBody_rev, DataLen_rev)  # 校验请求数据的长度
    res_check_crc = check_crc(DataBody_rev, CRC_rev)              # 校验请求数据CRC值
    if not res_check_datalen:
        ResCode = '-1'
        ErrData = '0x02'
        DataBody = {"ResCode":ResCode,'ErrData':ErrData}
    elif not res_check_crc:
        ResCode = '-1'
        ErrData = '0x03'
        DataBody = {"ResCode":ResCode,'ErrData':ErrData}
    else:
        DataBody = {"ResCode":'0'}
    HeadFlag = 'TUR'
    FuncCode = "S403"
    TID = data_dict.get("TID")
    send_result = send_to_server(HeadFlag, FuncCode, TID, DataBody)
    if send_result == 'Fail':
        logger.error('TUR-S403 response failed')
    else:
        logger.info('TUR-S403 response success')

    # -------- 把TU-S403发来的数据格式化输出 --------
    res = robot_output(DataBody_rev)
    return res


# 【把结果格式化输出到robot】
def robot_output(DataBody_rev):
    Status_dict = {'0':'成功', '-1':'失败/超时'}

    # -------- 结果输出到 Robot --------
    Event = DataBody_rev.get("Event")
    if (DataBody_rev.get("Result").get('Status') == '0'):
        Status = DataBody_rev.get("Result").get('Status')
        Status_CN = Status_dict.get(Status)
        Output = DataBody_rev.get("Result").get('Output')
        # Canlog = DataBody_rev.get("Result").get('Logfile').get('Canlog')
        # Someiplog = DataBody_rev.get("Result").get('Logfile').get('Someiplog')
        logger.info('case completed  -------------------------------------------------------------------\
                    \r\n用例【{}】, 已完成测试.\
                    \r\n接口调用【{}】. 接口返回值：{}.'\
                    .format(Event, Status_CN, Output))
        print('--------------------------------------------------------------------------------------')
        return 'Pass'
        # if Expect == Output:
        #     return 'Pass'
        # else:
        #     return 'Fail'

    elif (DataBody_rev.get("Result").get('Status') == '-1'):
        Status = DataBody_rev.get("Result").get('Status')
        Status_CN = Status_dict.get(Status)
        # Canlog = DataBody_rev.get("Result").get('Logfile').get('Canlog')
        # Someiplog = DataBody_rev.get("Result").get('Logfile').get('Someiplog')
        logger.info('case completed  -------------------------------------------------------------------\
                    \r\n用例【{}】, 已完成测试.\
                    \r\n接口调用【{}】.'\
                    .format(Event, Status_CN))
        print('--------------------------------------------------------------------------------------')
        return 'Fail'

    else:
        # Canlog = DataBody_rev.get("Result").get('Logfile').get('Canlog')
        # Someiplog = DataBody_rev.get("Result").get('Logfile').get('Someiplog')
        logger.info('case completed  -------------------------------------------------------------------\
                    \r\n用例【{}】, 已完成测试.'\
                    .format(Event))
        print('--------------------------------------------------------------------------------------')
        return 'Pass'


# 【对服务端发来的数据进行DataLen校验】
def check_datalen(DataBody_rev, DataLen_rev):
    DataLen = len(json.dumps(DataBody_rev).replace(' ','')) # int类型，如78
    DataLen = "%04d" % DataLen                              # 格式化成4字符str，如'0078'
    if DataLen == DataLen_rev:
        return True
    else:
        logger.error('DataLen check error! {} - {}'.format(DataLen, DataLen_rev))
        return False


# 【对服务端发来的数据进行CRC校验】
def check_crc(rev_databody, CRC_rev):
    crc = crc16_ccitt(rev_databody)
    if crc == CRC_rev:
        return True
    else:
        logger.error('crc check error! {} - {}'.format(crc, CRC_rev))
        return False


# 【CRC校验算法，计算DataBody的CRC值】
def crc16_ccitt(DataBody):
    DataBody = DataBody
    str_json = json.dumps(DataBody).replace(' ', '') # 数据转字符串,去空格
    bytes_data = str_json.encode()                   # 字符串转二进制字节
    len_data = len(str_json)                         # 计算字符串长度
    try:
        # 调用C语言库 'libsomeip_test.dll'或'libsomeip_test.so'
        crc_cdll = cdll.LoadLibrary('/home/ly/SOA_client/libsomeip_test.so')
        # crc_cdll = CDLL(r'./libsomeip_test.so')
    except Exception as e:
        logger.error('LoadLibrary failed:{}'.format(e))
    crc = crc_cdll.ly_crc(bytes_data, len_data)      # 库函数ly_crc()
    crc = hex(crc).upper()[2:].zfill(4)              # 格式要求: 16进制,大写,不要0x**标志
    return crc


def test_start():
    # 线程阻塞标识符，通过标识符判断服务端是否做出应答
    global event_S401, event_S402, event_S403
    event_S401 = Event()
    event_S402 = Event()
    event_S403 = Event()
    # 全局字典，用于多线程间数据传输，FuncCode做键，收到的内容做值
    global S401_DUR_dict, S402_DUR_dict, S403_DUR_dict
    S401_DUR_dict = {}
    S402_DUR_dict = {}
    S403_DUR_dict = {}

    global socket_client
    socket_client = create_connect()
    logger.info("Server connected √ ")

    # 子线程退出标识
    global thread_end_flag
    thread_end_flag = False

    global Event_TID
    Event_TID = 0     # 后面每下发一次S402，就会给Event_TID赋值一次

    t1 = Thread(target = DU_S401)
    t2 = Thread(target = recv_from_server)

    logger.info("Thread(DU_S401) start.")
    t1.start()
    logger.info("Thread(recv_from_server) start.")
    t2.start()

    time.sleep(2)
    logger.info("Heartbeat is normal. You can test! ")


def test_end():
    global socket_client
    global thread_end_flag
    # 一条测试用例交互完成，就退出子线程、断开socket_client
    thread_end_flag = True
    socket_client.close()
    logger.info('测试结束, Robot已与Linux服务端断开连接。')



if __name__ == "__main__":
    test_start()

    test_end()



